#include "pch.hpp"
#include "Win32Device.hpp"




#pragma warning (push)
#pragma warning (disable: 4355)
// Warning: use of this pointer in initializer list, but
// that is ok, as the this pointer is not used until
// construction is finished

Engine::Input::Win32Device::Win32Device(const EngineInfo &info)
	: m_logger(info.serviceProvider->service<logger::stream>())
	, m_window(info.window)
	, m_mouseHook(boost::bind(&Win32Device::mouseProc, this, _1, _2))
	, m_keyboardHook(boost::bind(&Win32Device::keyboardProc, this, _1, _2, _3))
	, m_size(info.displayMode.width, info.displayMode.height)
	, m_currentMouseWheelDelta(0.0f)
	, m_mouseWheelDelta(0.0f)
{
	m_logger->log_info("Initializing input device: Win32/Messages");

	// We create the map of all keys and
	// then query the key state once, to
	// get a defined state for all keys (pressed/not pressed).
	// Everything after that is done via windows messages
	setupPhysicalKeys();

	m_logger->log_info("input device completely initialized");
}
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma warning (pop)

Engine::Input::Win32Device::~Win32Device()
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




QString Engine::Input::Win32Device::apiName() const
{
	return "Win32/Messages";
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Input::Win32Device::update()
{
	int2 delta = m_currentMousePositionPixels - m_mousePositionPixels;

	// position of the mouse in pixels
	m_mousePositionPixels.x = m_currentMousePositionPixels.x;
	m_mousePositionPixels.y = m_currentMousePositionPixels.y;

	// position of the mouse, relative to the screen
	m_mousePositionRelative.x = static_cast<float>(m_mousePositionPixels.x) / m_size.x * 2 - 1;
	m_mousePositionRelative.y = 1 - static_cast<float>(m_mousePositionPixels.y) / m_size.y * 2;

	m_mouseDeltaPixels.x = delta.x;
	m_mouseDeltaPixels.y = delta.y;

	m_mouseDeltaRelative.x = static_cast<float>(delta.x / m_size.x * 2 - 1);
	m_mouseDeltaRelative.y = static_cast<float>(1- delta.y / m_size.x * 2);

	if(m_currentMouseWheelDelta != 0.0f)
	{
		m_mouseWheelDelta = m_currentMouseWheelDelta;
		m_currentMouseWheelDelta = 0.0f;
	}
	else
	{
		m_mouseWheelDelta = 0;
	}

	// The key-up/down state shall only be set
	// for one frame, so it can be used for clicking events
	// that trigger only once for each key-press. They
	// are set in the KeyboardProc function, so now we
	// have to unset them, as one frame has already passed.
	BOOST_FOREACH(KeyPair& p, m_keys)
	{
		Win32Key& key = p.second;

		// When the key is down, it's pressed state must be set to false (pressed triggers only once)
		if(key.down())    key.setPressed(false);
		// When the key is pressed however, the down state must be set to true (so the code above triggers the next update)
		if(key.pressed()) key.setDown(true);

		// Key the key is up, it's clicked state must be set to false (clicked triggers only once)
		if(!key.down())   key.setClicked(false);
		// When the key is clicked however, the down state must be set to false (so the code above triggers the next update)
		if(key.clicked()) key.setDown(false);

		if(key.pressed()) m_pressed(p.first);
		if(key.clicked()) m_clicked(p.first);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Input::Win32Device::resize(int width, int height)
{
	m_size.x = width;
	m_size.y = height;

	m_logger->log_debug(QString("Successfully resized to %1x%2 px").arg(width).arg(height));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Input::Win32Device::changeWindow(HWND window)
{
	m_window = window;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




int2 Engine::Input::Win32Device::mousePositionPixels() const
{
	return m_mousePositionPixels;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

int2 Engine::Input::Win32Device::mouseDeltaPixels() const
{
	return m_mouseDeltaPixels;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

float2 Engine::Input::Win32Device::mousePositionRelative() const
{
	return m_mousePositionRelative;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

float2 Engine::Input::Win32Device::mouseDeltaRelative() const
{
	return m_mouseDeltaRelative;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

float Engine::Input::Win32Device::mouseWheelDelta() const
{
	return m_mouseWheelDelta;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

const Key& Engine::Input::Win32Device::key(Key::Type k) const
{
	Keys::const_iterator i = m_keys.find(k);
	if(i == m_keys.end())
		BOOSTEXT_THROW(invalid_value_exception(k));

	return i->second;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Win32Key& Engine::Input::Win32Device::win32Key(Key::Type k)
{
	Keys::iterator i = m_keys.find(k);
	if(i == m_keys.end())
		BOOSTEXT_THROW(invalid_value_exception(k));

	return i->second;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Input::Win32Device::setupPhysicalKeys()
{
	m_keys.insert(KeyPair(Key::_0, Win32Key(0x30)));
	m_keys.insert(KeyPair(Key::_1, Win32Key(0x31)));
	m_keys.insert(KeyPair(Key::_2, Win32Key(0x32)));
	m_keys.insert(KeyPair(Key::_3, Win32Key(0x33)));
	m_keys.insert(KeyPair(Key::_4, Win32Key(0x34)));
	m_keys.insert(KeyPair(Key::_5, Win32Key(0x35)));
	m_keys.insert(KeyPair(Key::_6, Win32Key(0x36)));
	m_keys.insert(KeyPair(Key::_7, Win32Key(0x37)));
	m_keys.insert(KeyPair(Key::_8, Win32Key(0x38)));
	m_keys.insert(KeyPair(Key::_9, Win32Key(0x39)));

	m_keys.insert(KeyPair(Key::A, Win32Key(0x41)));
	m_keys.insert(KeyPair(Key::B, Win32Key(0x42)));
	m_keys.insert(KeyPair(Key::C, Win32Key(0x43)));
	m_keys.insert(KeyPair(Key::D, Win32Key(0x44)));
	m_keys.insert(KeyPair(Key::E, Win32Key(0x45)));
	m_keys.insert(KeyPair(Key::F, Win32Key(0x46)));
	m_keys.insert(KeyPair(Key::G, Win32Key(0x47)));
	m_keys.insert(KeyPair(Key::H, Win32Key(0x48)));
	m_keys.insert(KeyPair(Key::I, Win32Key(0x49)));
	m_keys.insert(KeyPair(Key::J, Win32Key(0x4A)));
	m_keys.insert(KeyPair(Key::K, Win32Key(0x4B)));
	m_keys.insert(KeyPair(Key::L, Win32Key(0x4C)));
	m_keys.insert(KeyPair(Key::M, Win32Key(0x4D)));
	m_keys.insert(KeyPair(Key::N, Win32Key(0x4E)));
	m_keys.insert(KeyPair(Key::O, Win32Key(0x4F)));
	m_keys.insert(KeyPair(Key::P, Win32Key(0x50)));
	m_keys.insert(KeyPair(Key::Q, Win32Key(0x51)));
	m_keys.insert(KeyPair(Key::R, Win32Key(0x52)));
	m_keys.insert(KeyPair(Key::S, Win32Key(0x53)));
	m_keys.insert(KeyPair(Key::T, Win32Key(0x54)));
	m_keys.insert(KeyPair(Key::U, Win32Key(0x55)));
	m_keys.insert(KeyPair(Key::V, Win32Key(0x56)));
	m_keys.insert(KeyPair(Key::W, Win32Key(0x57)));
	m_keys.insert(KeyPair(Key::X, Win32Key(0x58)));
	m_keys.insert(KeyPair(Key::Y, Win32Key(0x59)));
	m_keys.insert(KeyPair(Key::Z, Win32Key(0x5A)));

	m_keys.insert(KeyPair(Key::F1, Win32Key(VK_F1)));
	m_keys.insert(KeyPair(Key::F2, Win32Key(VK_F2)));
	m_keys.insert(KeyPair(Key::F3, Win32Key(VK_F3)));
	m_keys.insert(KeyPair(Key::F4, Win32Key(VK_F4)));
	m_keys.insert(KeyPair(Key::F5, Win32Key(VK_F5)));
	m_keys.insert(KeyPair(Key::F6, Win32Key(VK_F6)));
	m_keys.insert(KeyPair(Key::F7, Win32Key(VK_F7)));
	m_keys.insert(KeyPair(Key::F8, Win32Key(VK_F8)));
	m_keys.insert(KeyPair(Key::F9, Win32Key(VK_F9)));
	m_keys.insert(KeyPair(Key::F10, Win32Key(VK_F10)));
	m_keys.insert(KeyPair(Key::F11, Win32Key(VK_F11)));
	m_keys.insert(KeyPair(Key::F12, Win32Key(VK_F12)));

	m_keys.insert(KeyPair(Key::Space, Win32Key(VK_SPACE)));
	m_keys.insert(KeyPair(Key::LControl, Win32Key(VK_LCONTROL)));
	m_keys.insert(KeyPair(Key::RControl, Win32Key(VK_RCONTROL)));
	m_keys.insert(KeyPair(Key::LShift, Win32Key(VK_LSHIFT)));
	m_keys.insert(KeyPair(Key::RShift, Win32Key(VK_RSHIFT)));
	m_keys.insert(KeyPair(Key::Alt, Win32Key(VK_LMENU)));
	m_keys.insert(KeyPair(Key::AltGr, Win32Key(VK_RMENU)));
	m_keys.insert(KeyPair(Key::Caps, Win32Key(VK_CAPITAL)));
	m_keys.insert(KeyPair(Key::Tab, Win32Key(VK_TAB)));
	m_keys.insert(KeyPair(Key::Enter, Win32Key(VK_RETURN)));
	m_keys.insert(KeyPair(Key::Backspace, Win32Key(VK_BACK)));
	m_keys.insert(KeyPair(Key::Delete, Win32Key(VK_DELETE)));
	m_keys.insert(KeyPair(Key::Esc, Win32Key(VK_ESCAPE)));
	m_keys.insert(KeyPair(Key::Pause, Win32Key(VK_PAUSE)));
	m_keys.insert(KeyPair(Key::Print, Win32Key(VK_PRINT)));
	m_keys.insert(KeyPair(Key::Pos1, Win32Key(VK_HOME)));
	m_keys.insert(KeyPair(Key::End, Win32Key(VK_END)));
	m_keys.insert(KeyPair(Key::PageDown, Win32Key(VK_NEXT)));
	m_keys.insert(KeyPair(Key::PageUp, Win32Key(VK_PRIOR)));
	m_keys.insert(KeyPair(Key::Left, Win32Key(VK_LEFT)));
	m_keys.insert(KeyPair(Key::Up, Win32Key(VK_UP)));
	m_keys.insert(KeyPair(Key::Right, Win32Key(VK_RIGHT)));
	m_keys.insert(KeyPair(Key::Down, Win32Key(VK_DOWN)));

	m_keys.insert(KeyPair(Key::MouseLeft, Win32Key(VK_LBUTTON)));
	m_keys.insert(KeyPair(Key::MouseMiddle, Win32Key(VK_MBUTTON)));
	m_keys.insert(KeyPair(Key::MouseRight, Win32Key(VK_RBUTTON)));
	m_keys.insert(KeyPair(Key::MouseX1, Win32Key(VK_XBUTTON1)));
	m_keys.insert(KeyPair(Key::MouseX2, Win32Key(VK_XBUTTON2)));
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Engine::Input::Win32Device::mouseProc(WPARAM wParam, LPARAM lParam)
{
	switch(wParam)
	{
	case WM_MOUSEMOVE:
		{
			MOUSEHOOKSTRUCT* mouse = (MOUSEHOOKSTRUCT*)lParam;
			POINT pos = mouse->pt;
			POINT nPos = pos;

			// ScreenToClient must only be called when in windowed mode
			// The returned coordinates are just plain wrong when in fullscreen mode
			// I would suppose that either my window is not set to the right position
			// or that there is still some sort of bar
			if(ScreenToClient(m_window, &nPos))
			{
				m_currentMousePositionPixels.x = nPos.x;
				m_currentMousePositionPixels.y = nPos.y;
			}
		}
		break;
	case WM_MOUSEWHEEL:
		{
			MOUSEHOOKSTRUCTEX *mouseEx = (MOUSEHOOKSTRUCTEX*)lParam;
			short zDelta = static_cast<short>(HIWORD(mouseEx->mouseData));

			m_currentMouseWheelDelta = zDelta / 120.0f;
		}
		break;
	case WM_LBUTTONDBLCLK:
		{}
		break;
	case WM_LBUTTONDOWN:
		{
			Win32Key& k = win32Key(Key::MouseLeft);
			k.setPressed(true);
		}
		break;
	case WM_LBUTTONUP:
		{
			Win32Key& k = win32Key(Key::MouseLeft);
			k.setClicked(true);
		}
		break;
	case WM_MBUTTONDOWN:
		{
			Win32Key& k = win32Key(Key::MouseMiddle);
			k.setPressed(true);
		}
		break;
	case WM_MBUTTONUP:
		{
			Win32Key& k = win32Key(Key::MouseMiddle);
			k.setClicked(true);
		}
		break;
	case WM_RBUTTONDBLCLK:
		{}
		break;
	case WM_RBUTTONDOWN:
		{
			Win32Key& k = win32Key(Key::MouseRight);
			k.setPressed(true);
		}
		break;
	case WM_RBUTTONUP:
		{
			Win32Key& k = win32Key(Key::MouseRight);
			k.setClicked(true);
		}
		break;
	case WM_XBUTTONDBLCLK:
		{}
		break;
	case WM_XBUTTONDOWN:
		{
			uint16 btn = HIWORD(wParam);
			Win32Key& k = win32Key(btn == XBUTTON1 ? Key::MouseX1 : Key::MouseX2);
			k.setPressed(true);
		}
		break;
	case WM_XBUTTONUP:
		{
			uint16 btn = HIWORD(wParam);
			Win32Key& k = win32Key(btn == XBUTTON1 ? Key::MouseX1 : Key::MouseX2);
			k.setClicked(true);
		}
		break;
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Input::Win32Device::keyboardProc(int message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
	case WM_KEYDOWN:
		{
			Key::Type t = Win32Key::fromVirtualKey(wParam);
			if(t != Key::Unknown)
			{
				Win32Key& k = win32Key(t);
				k.setPressed(true);
			}
		}
		break;
	case WM_KEYUP:
		{
			Key::Type t = Win32Key::fromVirtualKey(wParam);
			if(t != Key::Unknown)
			{
				Win32Key& k = win32Key(t);
				k.setClicked(true);
			}
		}
		break;
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
